﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;

namespace CodeRepositoryConsole.OOP
{
    class AnonymousType
    {
        // see also https://stackoverflow.com/questions/6624811/how-to-pass-anonymous-types-as-parameters

        public void TestAnonymouMethodParameter()
        {
            var anonymous = ReturnsAnonymous();
            RetrieveAnonymousByDynamic(anonymous);
            RetrieveAnonymousByCastByName(anonymous);
            RetrieveAnonymousByReflection(anonymous);
        }

        /// <summary>
        /// <seealso cref="https://stackoverflow.com/questions/6466054/whats-the-return-type-of-an-anonymous-class#"/>
        /// </summary>
        /// <returns> 
        /// The object that you return does have a class, 
        /// but it's anonymous so you can't specify it in the code. You just have to return it as an object
        /// </returns>
        private object ReturnsAnonymous()
        {
            return new { Color = "Blue", Price = 123.23m };
        }


        private void RetrieveAnonymousByDynamic(dynamic obj)
        {
            Console.WriteLine("Retrieve By dynamic [Color:" + obj.Color + " Price:" + obj.Price + "]");
        }

        /// <summary>
        /// <seealso cref="https://stackoverflow.com/questions/6466054/whats-the-return-type-of-an-anonymous-class#"/>
        /// </summary>
        /// <param name="obj"></param>
        private void RetrieveAnonymousByCastByName(object obj)
        {
            /*
             * This trick only works when the anonymous source object and the anonymous example object come from the same assembly
             * 
             * reason:
             * 
             * If two or more anonymous object initializers in an assembly specify a sequence of properties that are in the same order and that have the same names and types,
             * the compiler treats the objects as instances of the same type. 
             * They share the same compiler-generated type information.
             * 
             * https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/anonymous-types
             */

            var example = new { Color = "", Price = 0m };
            var anon = CastByName(obj, example);
            Console.WriteLine("Retrieve By Cast By Example [Color:" + anon.Color + " Price:" + anon.Price + "]");
        }

        private T CastByName<T>(object source, T example) where T : class
        {
            return source as T;
        }

        private T CastByExample<T>(object obj, Func<T> example)
        {
            return (T)obj;
        }

        /// <summary>
        /// <seealso cref="https://stackoverflow.com/questions/8353306/how-to-pass-anonymous-type-to-method-as-a-parameter"/>
        /// </summary>
        /// <param name="obj"></param>
        private void RetrieveAnonymousByReflection(object obj)
        {
            Type type = obj.GetType();

            Console.WriteLine("Retrieve by reflection:");

            foreach (PropertyInfo propertyInfo in type.GetProperties())
            {
                Console.WriteLine(propertyInfo.Name + ":" + propertyInfo.GetValue(obj, null).ToString());
            }
        }

    }
}
